﻿using System.Collections.Generic;
using Godot.core;

namespace Godot.modules.gdscript
{
    public class GDInstance : ScriptInstance 
    {
        private class GDScriptMemberSort 
        {
	        public int index;
	        public StringName name;
        };

        internal bool _base_ref;
        internal GDScript _script;
        internal Object _owner;
        internal List<Variant> _members;
  
        public override Script script
        {
            get { return _script; }
        }

        public override ScriptLanguage language
        {
            get { return GDScriptLanguage.singleton; }
        }

        public GDInstance()
        {
            _owner = null;
            _base_ref = false;
        }

        public override bool set(StringName p_name, Variant p_value)
        {         
	        //member
	        {	        
                if (_script._member_indices.ContainsKey(p_name)) 
                {
			        _members[_script._member_indices[p_name]] = p_value;
			        return true;
		        }
	        }

	        GDScript sptr = _script;
	        while(sptr != null) 
            {
                if (sptr._member_functions.ContainsKey(GDScriptLanguage.singleton.strings._set)) 
                {
                    Variant name = p_name;
                    Variant[] args = new Variant[2]{name , p_value};

                    Variant.CallError err = new Variant.CallError();
                    Variant ret = sptr._member_functions[GDScriptLanguage.singleton.strings._set].call(this,args, ref err);
                    if (err.error == Variant.CallError.Error.CALL_OK && ret.type == Variant.Type.BOOL && ((bool)ret))
                        return true;
		        }
		        sptr = sptr._base;
	        }

	        return false;
        }

        public override bool get(StringName p_name, ref Variant r_ret)
        {
            GDScript sptr = _script;
	        while(sptr != null) 
            {
		        {
			        if (_script._member_indices.ContainsKey(p_name)) 
                    {
				        r_ret = _members[_script._member_indices[p_name]];
				        return true; //index found

			        }
		        }

		        {
			        if (_script._constants.ContainsKey(p_name)) 
                    {
				        r_ret = _script._constants[p_name];
				        return true; //index found

			        }
		        }

		        {
			        if (sptr._member_functions.ContainsKey(GDScriptLanguage.singleton.strings._get)) {

				        Variant name = p_name;
				        Variant[] args = new Variant[1]{ name };

				        Variant.CallError err = new Variant.CallError();
				        Variant ret = (sptr._member_functions[GDScriptLanguage.singleton.strings._get] as GDFunction).call((GDInstance)(this),args, ref err);
				        if (err.error == Variant.CallError.Error.CALL_OK && ret.type != Variant.Type.NIL) 
                        {
					        r_ret = ret;
					        return true;
				        }
			        }
		        }
		        sptr = sptr._base;
	        }

	        return false;
        }

        public override void get_property_list(List<PropertyInfo> p_properties)
        {
            GDScript sptr = _script;
            List<PropertyInfo> props = new List<PropertyInfo>();
            while (sptr != null)
            {
                GDFunction E = sptr._member_functions[GDScriptLanguage.singleton.strings._get_property_list];
                if (E != null)
                {
                    Variant.CallError err = new Variant.CallError();
			        Variant ret = E.call(this, null, ref err);
			        if (err.error == Variant.CallError.Error.CALL_OK) 
                    {
				        if (ret.type != Variant.Type.ARRAY) 
                        {
					        //ERR_EXPLAIN("Wrong type for _get_property list, must be an array of dictionaries.");
					        return;
				        }
				        List<Variant> arr = ret;
				        
                        for(int i = 0; i < arr.Count; i++) 
                        {
					        Dictionary<Variant, Variant> d = arr[i];
					        if(!d.ContainsKey("name")) continue;
					        if(!d.ContainsKey("type")) continue;

					        PropertyInfo pinfo = new PropertyInfo();
					        
                            pinfo.type = (Variant.Type)(int)d["type"]; 
					        if(pinfo.type < 0 || pinfo.type >= Variant.Type.VARIANT_MAX ) continue;
					        
                            pinfo.name = d["name"];
					        if(pinfo.name == "") continue;
					        
                            if (d.ContainsKey("hint"))
						        pinfo.hint = (PropertyHint)(int)d["hint"];
					        
                            if (d.ContainsKey("hint_string"))
						        pinfo.hint_string = d["hint_string"];
					        
                            if (d.ContainsKey("usage"))
						        pinfo.usage = d["usage"];

					        props.Add(pinfo);

				        }

			        }
                }

                //instance a fake script for editing the values

		        List<GDScriptMemberSort> msort = new List<GDScriptMemberSort>();
                foreach(KeyValuePair<StringName, PropertyInfo> El in sptr._member_info)
                {
			        GDScriptMemberSort ms = new GDScriptMemberSort();
			        if(!sptr._member_indices.ContainsKey(El.Key)) continue;
			        ms.index = sptr._member_indices[El.Key];
			        ms.name = El.Key;
			        msort.Add(ms);

		        }

                System.Comparison<GDScriptMemberSort> msort_cmp = delegate(GDScriptMemberSort a, GDScriptMemberSort b){ return a.index - b.index; };
                msort.Sort(msort_cmp);
                msort.Reverse();
		        
                for(int i = 0; i < msort.Count; i++) 
                {
			        props.Insert(0, sptr._member_info[msort[i].name]);
		        }

                sptr = sptr._base;
            }

	        foreach (PropertyInfo E in props) 
            {
		        p_properties.Add(E);
	        }
        }

        public override void get_method_list(ref List<MethodInfo> p_list)
        {
            GDScript sptr = _script;
	        while(sptr != null) 
            {
		        //for (Map<StringName,GDFunction>::Element *E = sptr->member_functions.front();E;E=E->next()) 
                foreach(KeyValuePair<StringName,GDFunction> E in sptr._member_functions) 
                {
			        MethodInfo mi = new MethodInfo();
			        mi.name = E.Key;
			        for(int i = 0; i < E.Value.get_argument_count(); i++)
				        mi.arguments.Add(new PropertyInfo(Variant.Type.NIL, "arg" + i));
			        p_list.Add(mi);
		        }
		        sptr = sptr._base;
	        }
        }

        public override bool has_method(StringName p_method)
        {
            GDScript sptr = _script;
	        while(sptr != null) 
            {
		        if (sptr._member_functions.ContainsKey(p_method))
			        return true;
		        sptr = sptr._base;
	        }

	        return false;
        }

        public override Variant call(StringName p_method, Variant[] p_args, Variant.CallError r_error)
        {
            GDScript sptr = _script;
	        while(sptr != null) 
            {
		        GDFunction E = sptr._member_functions[p_method];
		        if (E != null) 
                {
			        return E.call(this, p_args, ref r_error);
		        }
		        sptr = sptr._base;
	        }
	        r_error.error = Variant.CallError.Error.CALL_ERROR_INVALID_METHOD;
	        return new Variant();
        }

        public override void notification(int p_notification)
        {
            //notification is not virtual, it gets called at ALL levels just like in C.
	        Variant value = p_notification;
	        Variant[] args = new Variant[1]{ value };

	        GDScript sptr = _script;
	        while(sptr != null) 
            {
		        GDFunction E = sptr._member_functions[GDScriptLanguage.singleton.strings._notification];
		        if (E != null)
                {
			        Variant.CallError err = new Variant.CallError();
			        E.call(this, args, ref err);
			        if (err.error != Variant.CallError.Error.CALL_OK) 
                    {
				        //print error about notification call
			        }
		        }
		        sptr = sptr._base;
	        }
        }

        public void call_multilevel(StringName p_method, Variant[] p_args) 
        {
	        GDScript sptr = _script;
	        Variant.CallError ce = new Variant.CallError();

	        while(sptr != null) 
            {
		        GDFunction E = sptr._member_functions[p_method];
		        if (E != null) 
                {
			        E.call(this ,p_args, ref ce);
		        }
		        sptr = sptr._base;
	        }

        }

        public void call_multilevel_reversed(StringName p_method, Variant[] p_args) 
        {
	        if (_script != null) 
            {
		        _ml_call_reversed(_script, p_method, p_args);
	        }
        }

        protected void _ml_call_reversed(GDScript sptr, StringName p_method, Variant[] p_args) 
        {
	        if (sptr._base != null)
		        _ml_call_reversed(sptr._base, p_method, p_args);

	        Variant.CallError ce = new Variant.CallError();

	        GDFunction E = sptr._member_functions[p_method];
	        if (E != null) 
            {
		        E.call(this, p_args, ref ce);
	        }

        }
    }
}
